<template>
  <div class="wrapper">
    <div id="map_id"></div>
  </div>
</template>

<script setup lang="ts">
import * as THREE from 'three'
import { gsap } from 'gsap'
import * as d3 from 'd3'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { getDistrictGeoData } from '@/components/districtSearch/districtGeo'

let animateId

// 创建边界线
const createLine = (data, mapConfig, offsetXY, xishu) => {
  const points = [] as any
  data.forEach((item) => {
    const [x, y] = offsetXY(item)
    points.push(new THREE.Vector3(x, -y, 0))
  })

  const lineGeometry = new THREE.BufferGeometry().setFromPoints(points)
  const uplineMaterial = new THREE.LineBasicMaterial({
    color: '#5d70ba'
  })

  const path = new THREE.CatmullRomCurve3(points)
  // const { ...bottomLineMaterial } = mapConfig.bottomLine
  const tubeGeometry = new THREE.TubeGeometry(path, 200, 0.4 * xishu, 8, false)
  const material = new THREE.MeshBasicMaterial({
    color: '#4595e6'
  })
  const downTube = new THREE.Mesh(tubeGeometry, material)
  downTube.name = 'downTube'
  const upLine = new THREE.Line(lineGeometry, uplineMaterial)
  upLine.position.z = 4 * xishu + 0.01
  upLine.name = 'upLine'
  return [upLine, downTube]
}

// 创建地图板块
const createMesh = (data, mapConfig, offsetXY, blockData, blockConfig, name, center, xishu) => {
  const shape = new THREE.Shape()
  data.forEach((item, idx) => {
    console.log(offsetXY(item), idx)

    if (idx > 4800) return

    const [x, y] = offsetXY(item)

    if (idx === 0) shape.moveTo(x, -y)
    else shape.lineTo(x, -y)
  })

  console.log(shape, 'shape')

  const geometry = new THREE.ExtrudeGeometry(shape, {
    depth: 4 * xishu,
    bevelEnabled: false
  })
  const materialParams = {
    // 地图表面材质
    color: '#212e63',
    roughness: 0.35,
    metalness: 0.8
  }
  let blockTip = null
  if (blockData) {
    const blockItem = blockData.find((item) => item.name === name)
    if (blockConfig.colors && blockItem) {
      // 线性插值
      // 颜色转换
      const hexToRgb = (hex) => {
        // 去除可能的 # 符号
        hex = hex.replace(/^#/, '')
        // 将颜色值分解成三个部分：红色、绿色和蓝色
        const bigint = parseInt(hex, 16)
        const r = (bigint >> 16) & 255
        const g = (bigint >> 8) & 255
        const b = bigint & 255
        return { r, g, b }
      }
      // 线性插值函数
      const lerp = (start, end, t) => {
        return start * (1 - t) + end * t
      }
      const color1 = hexToRgb(blockConfig.colors[0])
      const color2 = hexToRgb(blockConfig.colors[1])
      const maxValue = Math.max(...blockData.map((item) => item.value))
      const minValue = Math.min(...blockData.map((item) => item.value))
      // 将数值映射到 [0, 1] 范围
      const tValue = (blockItem.value - minValue) / (maxValue - minValue)
      // 将最大到最小分为5等分，计算tValue所对应的等分t值，根据 t 值计算颜色插值的位置
      const index = Math.floor(tValue * 5)
      const tArr = [0, 0.25, 0.5, 0.75, 1, 1]
      const t = tArr[index]
      // 对每个颜色通道进行线性插值
      const interpolatedColor = {
        r: Math.round(lerp(color1.r, color2.r, t)),
        g: Math.round(lerp(color1.g, color2.g, t)),
        b: Math.round(lerp(color1.b, color2.b, t))
      }
      materialParams.color = `rgb(${interpolatedColor.r},${interpolatedColor.g},${interpolatedColor.b})`
    }

    // label 文字
    const div = document.createElement('div')
    Object.entries(blockConfig.labelStyle).forEach(([key, value]) => {
      div.style[key] = value
    })
    if (blockConfig.labelFormatter && blockItem) {
      blockItem.innerHTML = blockConfig.labelFormatter(blockItem)
    } else {
      div.innerHTML = [
        `<div style="font-weight: bold;margin-bottom: 8px;">${blockItem?.name}</div>`,
        ...(blockItem?.info || []).map(
          (item) => `
          <div style="display:flex">
            <span style="color:#ccc">${item.title}：</span>
            <span style="flex: 1">${item.content}</span>
          </div>`
        )
      ].join('')
    }
    const tip = new CSS2DObject(div)
    tip.name = 'block-tip-' + name
    const [x, y] = offsetXY(center)
    tip.position.set(x, -y, mapConfig.depth + 0.8)
    tip.visible = false
    tip.renderOrder = 2
    blockTip = tip
  }

  const material = new THREE.MeshStandardMaterial(materialParams)
  const sideMaterial = new THREE.MeshStandardMaterial({
    // 地图侧面材质
    color: '#111838',
    roughness: 0.8,
    metalness: 0.2
  })
  const mesh = new THREE.Mesh(geometry, [material, sideMaterial])
  mesh.position.set(0, 0, 0)
  mesh.name = 'map'
  if (blockTip) mesh.add(blockTip)
  return mesh
}

function init(geoData: any) {
  const scene = new THREE.Scene()

  const container = document.getElementById('map_id') as HTMLElement
  console.log(container)

  const WIDTH = container.clientWidth || 500
  const HEIGHT = container.clientHeight || 500
  const camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 1, 10000)
  camera.position.set(0, 400, 0)
  camera.lookAt(0, 0, 0)
  // // 设置进入的动画
  const cameraPosition = [0, 200, 200]
  gsap.to(camera.position, {
    duration: 4,
    x: cameraPosition[0],
    y: cameraPosition[1],
    z: cameraPosition[2],
    ease: 'power2.inOut'
  })

  const renderer = new THREE.WebGLRenderer({
    alpha: true, // 设置抗锯齿
    antialias: true,
    // 设置物理灯光模拟效果
    physicallyCorrectLights: true,
    // 设置对数深度缓冲区
    logarithmicDepthBuffer: true
  })
  // 设置渲染尺寸大小
  renderer.setSize(WIDTH, HEIGHT)

  const controls = new OrbitControls(camera, renderer.domElement)
  // 设置控制器阻尼
  controls.enableDamping = true
  controls.update()
  // //上下翻转的最大角度
  controls.maxPolarAngle = 1.2
  // //上下翻转的最小角度
  controls.minPolarAngle = 0
  // 禁止相机平移
  controls.enablePan = false

  const ambientLight = new THREE.AmbientLight('#fff', 4)
  scene.add(ambientLight)

  // 渲染元素
  const root = document.getElementById('map_id') as HTMLElement
  root.appendChild(renderer.domElement)

  // 8 为了方便观察3D图像，添加三位坐标对象
  const axes = new THREE.AxesHelper(400)
  scene.add(axes)

  const centerAndSize = getMapCenterAndSize(geoData)
  const mapCenter = centerAndSize.center
  // 42.398315999999994
  const xishu = centerAndSize.size / 42.4
  const map = new THREE.Object3D()
  // 地图的地理中心点，取配置的数据，没有配置的话取第一个区块的中心点
  const center = geoData.features[0].properties.centroid
  // 将经纬度解析为空间坐标
  const offsetXY = d3.geoMercator()
  console.log(center)

  offsetXY.center(center).translate([0, 0])
  let ripplesFlag = false // 解决重复绘制波纹的问题
  geoData.features.forEach((feature: any, index: number) => {
    const unit = new THREE.Object3D()
    const { centroid, center, name } = feature.properties
    const { coordinates, type } = feature.geometry
    const point = centroid || center || [0, 0]
    const depth = 16 //定义地图高度

    if (index > 12) return

    coordinates.forEach((coordinate: any) => {
      if (type === 'MultiPolygon') coordinate.forEach((item: any) => fn(item))
      if (type === 'Polygon') fn(coordinate)

      function fn(coordinate: any) {
        unit.name = name
        // unit._type = 'block'
        // unit._properties = feature.properties
        const mesh = createMesh(coordinate, {}, offsetXY, null, {}, name, point, xishu)
        const line = createLine(coordinate, {}, offsetXY, xishu)
        unit.add(mesh, ...line)
      }
    })

    map.add(unit)
  })
  setCenter(map)

  // const { size } = getMapCenterAndSize(geoData)
  const mapScale = 1
  map.scale.set(mapScale, mapScale, mapScale)
  map.position.set(20, 0, 0)
  scene.add(map)

  console.log(map)

  // 设置动画
  const animate = () => {
    animateId = requestAnimationFrame(animate)
    controls.update()
    camera.lookAt(scene.position)
    // 场景旋转
    scene.rotation.y += 0
    // 使用渲染器渲染相机看这个场景的内容渲染出来
    renderer.render(scene, camera)
  }
  animate()
}

// 获取地图中心点
const getMapCenterAndSize = (geoData) => {
  console.log(geoData)

  const points = geoData.features.map((feature) => {
    const { centroid, center } = feature.properties
    const point = centroid || center || [0, 0]
    return new THREE.Vector2(point[0], point[1])
  })
  const box = new THREE.Box2()
  points.forEach((point) => box.expandByPoint(point))
  const center = box.getCenter(new THREE.Vector2())
  const size = box.getSize(new THREE.Vector2())

  console.log(center, size, Math.max(size.x, size.y))

  if (size.x === 0 && size.y === 0 && geoData.features[0].geometry.coordinates[0][0]) {
    const box2 = new THREE.Box2()
    geoData.features[0].geometry.coordinates[0][0]
      .map((item) => new THREE.Vector2(item[0], item[1]))
      .forEach((point) => box2.expandByPoint(point))
    const size2 = box2.getSize(new THREE.Vector2())
    size.x = size2.x
    size.y = size2.y
  }
  return {
    center: [center.x, center.y],
    size: Math.max(size.x, size.y) || 3
  }
}

// 获取区块中心点
const setCenter = (map) => {
  map.rotation.x = -Math.PI / 2
  const box = new THREE.Box3().setFromObject(map)
  const center = box.getCenter(new THREE.Vector3())

  console.log(center)

  const offset = [0, 0]
  map.position.x = map.position.x - center.x - offset[0]
  map.position.z = map.position.z - center.z - offset[1]
}

onMounted(async () => {
  const res = await getDistrictGeoData('100000')
  console.log(res)

  init(res)
})
</script>

<style lang="less" scoped>
#map_id {
  width: 100vw;
  height: 100vh;
}
</style>
